package org.odst.formula;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.odftoolkit.odfdom.doc.table.OdfTableCell;
import org.odftoolkit.odfdom.doc.table.OdfTableRow;
import org.odst.parser.Expression;
import org.odst.tag.Point;
import org.odst.transformation.BlockTransformation;
import org.odst.transformation.DuplicateTransformation;
import org.odst.transformer.Workbook;

/**
 * @author Leonid Vysochyn
 * @author Cyril PODER
 */
public class FormulaControllerImpl implements FormulaController {
    protected final Log log = LogFactory.getLog(getClass());

    protected Map<String, List<Formula>> sheetFormulasMap;


    Workbook workbook;

    public FormulaControllerImpl(Workbook workbook) {
        this.workbook = workbook;
        sheetFormulasMap = workbook.createFormulaSheetMap();
    }
    
    private List<Expression> parseExpressions(String value, Map<String, Object> beans, org.odst.transformer.Configuration configuration) {
    	List<Expression> expressions = new ArrayList<Expression>();
    	String curValue = new String(value);
		try {
			while (curValue.length() > 0) {
				int i = curValue.indexOf(configuration
						.getStartExpressionToken());
				if (i >= 0) {
					int k = curValue.indexOf(configuration
							.getEndExpressionToken(), i + 2);
					if (k >= 0) {
						// new bean property found
						String expr = curValue.substring(i + 2, k);
						if (i > 0) {
							String before = curValue.substring(0, i);
							expressions.add(
									new Expression(before, configuration));
						}
						Expression expression = new Expression(expr, beans,
								configuration);
						expressions.add(expression);
						curValue = curValue.substring(k + 1, curValue.length());
					} else {
						expressions.add(
								new Expression(curValue, configuration));
						curValue = "";
					}
				} else {
					if (curValue.length() != value.length()) {
						expressions.add(
								new Expression(curValue, configuration));
					}
					curValue = "";
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("Can't parse expression", e);
		}
		
		return expressions;
    }

    public void updateWorkbookFormulas(BlockTransformation transformation){
//        Set<String> sheetNames = sheetFormulasMap.keySet();
        Formula newFormula;
        Set<CellRef> cellRefs, newCellRefs;
        CellRef newCellRef;
        List<String> resultCells;
        String newCell;
        Point point, newPoint;
        Set<CellRef> cellRefsToRemove = new HashSet<CellRef>();
        Set<Formula> formulasToRemove = new HashSet<Formula>();
        String sheetName = transformation.getBlock().getSheet().getSheetName();
//        for (String sheetName: sheetNames) {
//            List<Formula> formulas = sheetFormulasMap.get( sheetName );
            formulasToRemove.clear();
            for (Formula formula : new ArrayList<Formula>(sheetFormulasMap.get( sheetName ))) {
                List<Point> formulaPoints = null;
                Point formulaPoint = null;
                boolean transformFormula = false;
                if( formula.getSheet().getSheetName().equals( transformation.getBlock().getSheet().getSheetName() ) ){
                    if (!transformation.getBlock().getSheet().getBeanParams().isEmpty() && !formula.isInline()) {
    		            List<Expression> expressions = parseExpressions(formula.getFormula(), transformation.getBlock().getSheet().getBeanParams(), transformation.getBlock().getSheet().getConfiguration());
    		    		if (expressions.size() > 0) {
    		    			String value = "";
		    				try {
	    		    			for (Expression expr : expressions) {
	    		    					value += expr.evaluate();
	    		    			}
	    		    			formula.setFormula(value);
	    		    			formula.parseFormula();
		    				} catch (Exception e) {
		    					e.printStackTrace();
		    				}
    		    		}
                    }
                    transformFormula = true;
                    formulaPoint = new Point( formula.getRowNum().intValue(), formula.getCellNum().shortValue() );
                    formulaPoints = transformation.transformCell( formulaPoint );
                }
                if( !transformFormula || (formulaPoints != null && !formulaPoints.isEmpty())){
                cellRefs = formula.getCellRefs();
                cellRefsToRemove.clear();
                for (CellRef cellRef : cellRefs) {
                    if( !(transformation instanceof DuplicateTransformation && transformation.getBlock().contains(cellRef) &&
                            transformation.getBlock().contains( formula ) ) ){
                        resultCells = transformation.transformCell( sheetName, cellRef );
                        if( resultCells != null ){
                            if( resultCells.size() == 1 ){
                                newCell = (String) resultCells.get(0);
                                cellRef.update( newCell );
                            }else if( resultCells.size() > 1 ){
                                cellRef.update( resultCells );
                            }
                        }else {
                            cellRefsToRemove.add( cellRef );
                        }
                    }
                }
//                cellRefs.removeAll( cellRefsToRemove );
                if( !cellRefsToRemove.isEmpty() ){
                    formula.removeCellRefs( cellRefsToRemove );
                }
                formula.updateReplacedRefCellsCollection();
                    formula.updateCellRefs();
                    if(formulaPoints != null && !formulaPoints.isEmpty()){
                        if(formulaPoints.size() == 1){
                            newPoint = (Point) formulaPoints.get(0);
                            formula.setRowNum( new Integer( newPoint.getRow() ));
                            formula.setCellNum( new Integer( newPoint.getCol() ));
                        }else{
                            List<Formula> sheetFormulas = sheetFormulasMap.get( formula.getSheet().getSheetName() );
                            for (int j = 1, num = formulaPoints.size(); j < num; j++) {
                                point = (Point) formulaPoints.get(j);
                                newFormula = new Formula( formula );
                                newFormula.setRowNum( new Integer(point.getRow()) );
                                newFormula.setCellNum( new Integer(point.getCol() ) );
                                newCellRefs = newFormula.getCellRefs();
                                for (Iterator<CellRef> iterator1 = newCellRefs.iterator(); iterator1.hasNext();) {
                                    newCellRef =  (CellRef) iterator1.next();
                                    if( transformation.getBlock().contains( newCellRef ) && transformation.getBlock().contains( formulaPoint ) ){
                                        newCellRef.update(transformation.getDuplicatedCellRef( sheetName, newCellRef.toString(), j));
                                    }
                                }
                                sheetFormulas.add( newFormula );
                            }
                        }
                    }
                }else{
                    if( formulaPoints == null ){
                        // remove formula
                        formulasToRemove.add( formula );
                    }
                }
            }
            sheetFormulasMap.get( sheetName ).removeAll(formulasToRemove);
//            formulas.removeAll( formulasToRemove );
//        }
    }

    public Map<String, List<Formula>> getSheetFormulasMap() {
        return sheetFormulasMap;
    }

    public void writeFormulas(FormulaResolver formulaResolver) {
        Set<String> sheetNames = sheetFormulasMap.keySet();
        for (Iterator<String> iterator = sheetNames.iterator(); iterator.hasNext();) {
            String sheetName =  (String) iterator.next();
            List<Formula> formulas = sheetFormulasMap.get( sheetName );
            for (int i = 0; i < formulas.size(); i++) {
                Formula formula = (Formula) formulas.get(i);
                String formulaString = formulaResolver.resolve( formula, null);
                OdfTableCell cell = null;
				try {
					OdfTableRow row = formula.getSheet().getOooSheet().getRowByIndex(formula.getRowNum());
					if (row != null) {
						cell = row.getCellByIndex(formula.getCellNum());
					} else {
						System.err.println("Row is null !");
					}
				} catch (IndexOutOfBoundsException e1) {
					e1.printStackTrace();
				}
                if (formulaString != null) {
                    try {
                        cell.setFormula("=" + formulaString);
                        cell.setStringValue("=" + formulaString);
                    } catch (RuntimeException e) {
                        log.error("Can't set formula: " + formulaString, e);
//                        hssfCell.setCellType( HSSFCell.CELL_TYPE_BLANK );
                        throw new RuntimeException("Can't set formula: " + formulaString, e );
                    }
                }
            }
        }
    }

 }
